/*-------------------------------------------------------------------------
 *
 * walwriter.c
 *
 * WAL 写入后台进程自 Postgres 8.3 起引入。它尝试避免常规后端需要写出（并 fsync）WAL 页面。此外，它保证在事务提交记录在提交时未立即同步到磁盘（即“异步提交”）的情况下，这些记录将在一个可知的时间内到达磁盘——实际上，这个时间最多是 wal_writer_delay 循环时间的三倍。
 *
 * 请注意，与共享缓存的 bgwriter 一样，当 walwriter 跟不上时，常规后端仍然可以发出 WAL 写入和 fsync。这意味着 WALWriter 不是一个必需的进程，可以在请求时快速关闭。
 *
 * 由于 walwriter 的周期与异步提交事务保证提交之前的最大延迟直接相关，因此在其上加载附加功能可能是不明智的。例如，如果你希望更提前地创建 xlog 段，最好在 bgwriter 中而不是 walwriter 中完成。
 *
 * walwriter 在启动子进程完成后由 Postmaster 启动。它保持活动状态，直到 Postmaster 命令其终止。正常终止是通过 SIGTERM，这指示 walwriter 退出(0)。紧急终止是通过 SIGQUIT；就像任何后端一样，walwriter 在 SIGQUIT 时将简单地中止并退出。
 *
 * 如果 walwriter 意外退出，Postmaster 会将其视为后端崩溃：共享内存可能会损坏，因此剩余后端应通过 SIGQUIT 被杀死，然后启动恢复周期。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/walwriter.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <unistd.h>

#include "access/xlog.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/walwriter.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/smgr.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
#include "utils/resowner.h"


/*
 * GUC参数
 */
int			WalWriterDelay = 200;
int			WalWriterFlushAfter = 128;

/*
 * 在延长延迟时间之前，不执行任何操作的循环次数，以及
 * 在我们决定进入休眠状态时应用于 WalWriterDelay 的乘数。
 * （也许这些需要可配置？）
 */
#define LOOPS_UNTIL_HIBERNATE		50
#define HIBERNATE_FACTOR			25

/* 私有函数的原型 */
static void fc_HandleWalWriterInterrupts(void);

/*
 * walwriter 进程的主入口点
 *
 * 这是从 AuxiliaryProcessMain 调用的，该过程已经创建了
 * 基本执行环境，但尚未启用信号。
 */
void WalWriterMain(void)
{
	sigjmp_buf	fc_local_sigjmp_buf;
	MemoryContext fc_walwriter_context;
	int			fc_left_till_hibernate;
	bool		fc_hibernating;

	/*
	 * 正确地接受或忽略 postmaster 可能发送给我们的信号
	 *
	 * 我们目前对 SIGINT 没有特别的使用，但似乎
	 * 将其视为 SIGTERM 是合理的。
	 */
	pqsignal(SIGHUP, SignalHandlerForConfigReload);
	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
	/* SIGQUIT 处理程序已经由 InitPostmasterChild 设置 */
	pqsignal(SIGALRM, SIG_IGN);
	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
	pqsignal(SIGUSR2, SIG_IGN); /* 未使用 */

	/*
	 * 重置一些由postmaster接受但这里不接受的信号
	 */
	pqsignal(SIGCHLD, SIG_DFL);

	/*
	 * 创建一个内存上下文，我们将所有工作都在这里完成。我们这样做是为了
	 * 在错误恢复期间重置上下文，从而避免
	 * 可能的内存泄漏。以前这段代码在
	 * TopMemoryContext中运行，但重置它会是个非常糟糕的主意。
	 */
	fc_walwriter_context = AllocSetContextCreate(TopMemoryContext,
											  "Wal Writer",
											  ALLOCSET_DEFAULT_SIZES);
	MemoryContextSwitchTo(fc_walwriter_context);

	/*
	 * 如果遇到异常，处理将在这里恢复。
	 *
	 * 你可能会想为什么这不是在一个围绕
	 * PG_TRY构造的无限循环中编码的。原因是这是异常堆栈的底部，
	 * 因此在CATCH部分的过程中完全没有异常处理程序
	 * 生效。通过保持最外层的setjmp
	 * 始终处于活动状态，我们至少有一些机会在错误
	 * 恢复期间恢复错误。（如果由此陷入无限循环，它将很快因overflow
	 * elog.c的内部状态堆栈而停止。）
	 *
	 * 请注意，我们使用sigsetjmp(..., 1)，因此当长跳转到这里时，
	 * 当前信号掩码（即BlockSig）将被恢复。因此，
	 * 除了SIGQUIT之外的信号将被阻止，直到我们完成错误
	 * 恢复。这一政策看似使HOLD_INTERRUPTS()
	 * 调用变得多余，但实际上并非如此，因为InterruptPending可能
	 * 已经被设置。
	 */
	if (sigsetjmp(fc_local_sigjmp_buf, 1) != 0)
	{
		/* 由于未使用 PG_TRY，必须手动重置错误堆栈 */
		error_context_stack = NULL;

		/* 在清理期间防止中断 */
		HOLD_INTERRUPTS();

		/* 将错误报告到服务器日志 */
		EmitErrorReport();

		/*
		 * 这些操作实际上只是 AbortTransaction() 的最小子集。
		 * 在 walwriter 中，我们担心的资源不多，
		 * 但我们有 LWLocks，也许还有缓冲区？
		 */
		LWLockReleaseAll();
		ConditionVariableCancelSleep();
		pgstat_report_wait_end();
		AbortBufferIO();
		UnlockBuffers();
		ReleaseAuxProcessResources(false);
		AtEOXact_Buffers(false);
		AtEOXact_SMgr();
		AtEOXact_Files(false);
		AtEOXact_HashTables(false);

		/*
		 * 现在返回到正常的顶级上下文并清除 ErrorContext，以便下次使用。
		 */
		MemoryContextSwitchTo(fc_walwriter_context);
		FlushErrorState();

		/* 刷新顶级上下文中任何泄漏的数据 */
		MemoryContextResetAndDeleteChildren(fc_walwriter_context);

		/* 现在我们可以再次允许中断 */
		RESUME_INTERRUPTS();

		/*
		 * 在任何错误后至少睡眠1秒。写入错误很可能会
		 * 重复发生，我们不想尽可能快地填满错误日志。
		 */
		pg_usleep(1000000L);

		/*
		 * 在任何错误后关闭所有打开的文件。这对Windows很有帮助，
		 * 因为保持已删除的文件打开会导致各种奇怪的错误。
		 * 目前不清楚我们是否需要在其他地方这样做，但应该不会有坏处。
		 */
		smgrcloseall();
	}

	/* 我们现在可以处理 ereport(ERROR) */
	PG_exception_stack = &fc_local_sigjmp_buf;

	/*
	 * 解锁信号（它们在postmaster分叉我们时被阻止）
	 */
	PG_SETMASK(&UnBlockSig);

	/*
	 * 在任何错误后重置休眠状态。
	 */
	fc_left_till_hibernate = LOOPS_UNTIL_HIBERNATE;
	fc_hibernating = false;
	SetWalWriterSleeping(false);

	/*
	 * 宣传我们的闩锁，后端可以用它在我们
	 * 睡眠时唤醒我们。
	 */
	ProcGlobal->walwriterLatch = &MyProc->procLatch;

	/*
	 * 永远循环
	 */
	for (;;)
	{
		long		fc_cur_timeout;

		/*
		 * 宣布我们可能在这个周期内进入休眠状态。 我们在重置闩之前这样做
		 * 以确保任何异步提交将看到设置的标志，如果他们可能需要
		 * 唤醒我们，并且我们不会错过他们发送的任何信号。 （如果我们在最后一个周期发现
		 * 在我们进入休眠状态之前需要做的工作，全球标志
		 * 将不必要地设置，但不会造成太大伤害。）但如果
		 * 不需要更改，则避免触摸全局标志。
		 */
		if (fc_hibernating != (fc_left_till_hibernate <= 1))
		{
			fc_hibernating = (fc_left_till_hibernate <= 1);
			SetWalWriterSleeping(fc_hibernating);
		}

		/* 清除任何已经等待的唤醒 */
		ResetLatch(MyLatch);

		/* 处理最近收到的任何信号 */
		fc_HandleWalWriterInterrupts();

		/*
		 * 做我们要做的事情；然后，如果 XLogBackgroundFlush() 找到了有用的
		 * 工作要做，重置休眠计数器。
		 */
		if (XLogBackgroundFlush())
			fc_left_till_hibernate = LOOPS_UNTIL_HIBERNATE;
		else if (fc_left_till_hibernate > 0)
			fc_left_till_hibernate--;

		/* 向累积统计系统报告待处理的统计信息 */
		pgstat_report_wal(false);

		/*
		 * 睡眠直到我们被信号唤醒或 WalWriterDelay 已经过期。 如果我们
		 * 已经很长时间没有做任何有用的事情，延长睡眠时间，以减少
		 * 服务器的闲置功耗。
		 */
		if (fc_left_till_hibernate > 0)
			fc_cur_timeout = WalWriterDelay;	/* in ms */
		else
			fc_cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;

		(void) WaitLatch(MyLatch,
						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
						 fc_cur_timeout,
						 WAIT_EVENT_WAL_WRITER_MAIN);
	}
}

/*
 * WAL writer 进程主循环的中断处理程序。
 */
static void fc_HandleWalWriterInterrupts(void)
{
	if (ProcSignalBarrierPending)
		ProcessProcSignalBarrier();

	if (ConfigReloadPending)
	{
		ConfigReloadPending = false;
		ProcessConfigFile(PGC_SIGHUP);
	}

	if (ShutdownRequestPending)
	{
		/*
		 * 在进程退出时强制报告剩余的 WAL 统计信息。
		 *
		 * 由于在主循环中调用 pgstat_report_wal 时 'force' 为 false
		 * 以避免过度负载累积统计系统，可能未报告 WAL writer 的统计计数器。
		 */
		pgstat_report_wal(true);

		proc_exit(0);
	}

	/* 执行此进程的内存上下文日志记录 */
	if (LogMemoryContextPending)
		ProcessLogMemoryContextInterrupt();
}
